package opn.srn.command;


import java.io.FileReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import opn.srn.grammar.commands.CommandsLexer;
import opn.srn.grammar.commands.CommandsParser;
import opn.srn.grammar.commands.CommandsParser.CommandContext;
import opn.srn.grammar.commands.CommandsParser.CommandsContext;
import opn.srn.grammar.commands.CommandsParser.OuthandlerContext;
import opn.srn.grammar.commands.CommandsParser.OuthandlersContext;
import opn.srn.grammar.commands.CommandsParser.ParameterContext;
import opn.srn.grammar.commands.CommandsParser.ParametersContext;

import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;

public class CommandHelper {
	public static ICommand<?> load(String commandFilename) throws Exception {
		CommandsLexer lexer = new CommandsLexer(new ANTLRInputStream(new FileReader(commandFilename)));
		CommonTokenStream tokens = new CommonTokenStream(lexer);
		CommandsParser p = new CommandsParser(tokens);
		p.setBuildParseTree(true);
		ParseTree tree = p.format();
		if (p.getNumberOfSyntaxErrors() != 0) {
			throw new Exception("Syntax error in command file " + commandFilename);
		}

		ICommand<?> command = parseFormat(tree);
		return command;
	}

	public static ICommand<?> parseFormat(ParseTree tree) throws Exception {
		ICommand<?> startCommand = createCommand((CommandContext) tree.getChild(0));

		List<ICommand<?>> commands = parseOuthandlers((OuthandlersContext) tree.getChild(2));

		for (ICommand<?> destination : commands) {
			startCommand.addOutHandler(destination);
		}

		return startCommand;
	}

	private static List<ICommand<?>> parseOuthandlers(OuthandlersContext outhandlers) throws Exception {
		int outHandlerCount = (outhandlers.getChildCount() + 1) / 2;
		List<ICommand<?>> firstOutHandlers = null;
		List<ICommand<?>> prevOutHandlers = null;
		for (int index = 0; index < outHandlerCount; index++) {
			List<ICommand<?>> outHandlerCmds = parseOuthandler((OuthandlerContext) outhandlers.getChild(index * 2));
			if (index == 0) {
				firstOutHandlers = outHandlerCmds;
			}
			if (prevOutHandlers != null) {
				linkOutHandlers(prevOutHandlers, outHandlerCmds);
			}
			prevOutHandlers = outHandlerCmds;
		}
		return firstOutHandlers;
	}

	private static void getLeafs(ICommand<?> source, List<ICommand<?>> leafCommands) {
		List<ICommand<?>> outHandlers = source.getOutHandlers();
		if (outHandlers.size() == 0) {
			leafCommands.add(source);
			return;
		}

		for (ICommand<?> outHandler : outHandlers) {
			getLeafs(outHandler, leafCommands);
		}
	}

	private static void linkOutHandlers(List<ICommand<?>> sources, List<ICommand<?>> targets) {
		for (ICommand<?> source : sources) {
			List<ICommand<?>> leafCommands = new ArrayList<ICommand<?>>();
			getLeafs(source, leafCommands);
			for (ICommand<?> leafCommand : leafCommands) {
				for (ICommand<?> target : targets) {
					leafCommand.addOutHandler(target);
				}
			}
		}
	}

	private static List<ICommand<?>> parseOuthandler(OuthandlerContext outhandler) throws Exception {
		int count = outhandler.getChildCount();

		List<ICommand<?>> cmds = new ArrayList<ICommand<?>>();
		for (int index = 0; index < count; index++) {
			ParseTree item = outhandler.getChild(index);
			if (item instanceof CommandContext) {
				cmds.add(createCommand((CommandContext) item));
			} else {
				cmds.addAll(createCommands((CommandsContext) item));
			}
		}

		return cmds;
	}

	private static List<ICommand<?>> createCommands(CommandsContext cmds) throws Exception {
		int count = (cmds.getChildCount() - 2 + 1) / 2;
		List<ICommand<?>> commands = new ArrayList<ICommand<?>>();
		for (int index = 0; index < count; index++) {
			commands.addAll(parseOuthandlers((OuthandlersContext) cmds.getChild(index * 2 + 1)));
		}
		return commands;
	}

	@SuppressWarnings("unchecked")
	private static ICommand<?> createCommand(CommandContext cmd) throws Exception {
		String commandName = cmd.getChild(0).getText();
		Map<String, String> parameters = createParameters((ParametersContext) cmd.getChild(1));
		ICommand<Map<String, String>> command = (ICommand<Map<String, String>>) CommandFactory.get().newInstance(
				commandName);
		command.load(parameters);
		return command;
	}

	private static Map<String, String> createParameters(ParametersContext child) {
		int parametersCount = (child.getChildCount() - 2 + 1) / 2;

		if (parametersCount == 0) {
			return null;
		}

		Map<String, String> parameters = new HashMap<String, String>();
		for (int index = 0; index < parametersCount; index++) {
			addToParameter((ParameterContext) child.getChild(index * 2 + 1), parameters);
		}
		return parameters;
	}

	private static void addToParameter(ParameterContext child, Map<String, String> parameters) {
		String key = child.getChild(1).getChild(0).getText();
		TerminalNode valueNode = (TerminalNode) child.getChild(3).getChild(0);

		int valueType = valueNode.getSymbol().getType();
		String value = valueNode.getText().trim();
		if (valueType == CommandsParser.STRING) {
			value = value.substring(1, value.length() - 1);
		}

		parameters.put(key, value);
	}

}
